package com.bidevalution.support.websocket;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.bidevalution.support.context.ChannelContext;
import com.bidevalution.support.context.Constant;
import com.bidevalution.support.context.RequestObject;
import com.bidevalution.support.context.ResponseObject;
import com.bidevalution.support.context.WebSocketOpTypeEnum;
import com.bidevalution.support.factory.WebSocketOpType;
import com.bidevalution.support.factory.WebSocketService;
import com.google.common.collect.Maps;
import com.qtp.core.util.GsonUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

@Component
@Sharable
public class WebSocketServerHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

	private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServerHandler.class);

	@Autowired
	private WebSocketService webSocketService;
	private Map<String, Integer> countMap = Maps.newConcurrentMap();

	/**
	 * 描述：读取完连接的消息后，对消息进行处理。 这里主要是处理WebSocket请求
	 */
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception {
		handlerWebSocketFrame(ctx, msg);
	}

	/**
	 * 定时发送消息到前端客户端，客户端发送确认消息到服务端
	 * <p>
	 * Description:
	 * </p>
	 * 
	 * @param ctx
	 * @param evt
	 * @throws Exception
	 * @see io.netty.channel.ChannelInboundHandlerAdapter#userEventTriggered(io.netty.channel.ChannelHandlerContext,
	 *      java.lang.Object)
	 */
	@Override
	public void userEventTriggered(final ChannelHandlerContext ctx, Object evt) throws Exception {

		if (evt instanceof IdleStateEvent) {
			IdleStateEvent event = (IdleStateEvent) evt;
			if (event.state() == IdleState.READER_IDLE) {
				Integer countInteger = countMap.get(ctx.channel().id().toString());
				if (countInteger == null) {
					countMap.put(ctx.channel().id().toString(), 1);
				} else {
					if (countInteger > 1) {
						webSocketService.remove(ctx.channel().id().toString());
						ctx.close();
						countMap.remove(ctx.channel().id().toString());
						LOGGER.info("关闭这个不活跃通道！");
					} else {
						countMap.put(ctx.channel().id().toString(), countInteger + 1);
					}
				}
			} else if (event.state() == IdleState.WRITER_IDLE) {
				ChannelContext channelContext = webSocketService.getChannelContext(ctx.channel().id().toString());
				if (channelContext != null) {
					webSocketService.send(channelContext, new ResponseObject(WebSocketOpTypeEnum.IDLE));
				}
			}
		} else {
			super.userEventTriggered(ctx, evt);
		}
	}

	/**
	 * 描述：处理WebSocketFrame
	 * 
	 * @param ctx
	 * @param frame
	 * @throws Exception
	 */
	private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {

		// 关闭请求
		if (frame instanceof CloseWebSocketFrame) {
			WebSocketServerHandshaker handshaker = Constant.webSocketHandshakerMap.get(ctx.channel().id().asLongText());
			if (handshaker == null) {
				sendErrorMessage(ctx, "不存在的客户端连接！");
			} else {
				handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
			}
			return;
		}
		// ping请求
		if (frame instanceof PingWebSocketFrame) {
			ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
			return;
		}
		// 只支持文本格式，不支持二进制消息
		if (!(frame instanceof TextWebSocketFrame)) {
			sendErrorMessage(ctx, "仅支持文本(Text)格式，不支持二进制消息");
		}

		// 客服端发送过来的消息
		String request = ((TextWebSocketFrame) frame).text();
		try {
//			InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
//	        LOGGER.info(ctx.channel().id().asLongText());
//	        LOGGER.info(insocket.getAddress().getHostAddress());
//	        LOGGER.info(insocket.getAddress().getHostName());
			if ("undefined".equals(request)) {
				return;
			}
			Map<String, Object> requestMap = GsonUtil.jsonToMapWithGson(request);
			if (StringUtils.isEmpty(requestMap.get("opType"))) {
				ctx.channel().writeAndFlush(
						new TextWebSocketFrame(GsonUtil.toJson(new ResponseObject(null).error("参数opType值为空"))));
				return;
			}
			if (StringUtils.isEmpty(requestMap.get("accessToken"))) {
				ctx.channel().writeAndFlush(
						new TextWebSocketFrame(GsonUtil.toJson(new ResponseObject(null).error("参数accessToken值为空"))));
				return;
			}
			String receiveKey = (String)requestMap.get("receiveKey");
			RequestObject requestObject = new RequestObject(requestMap.get("accessToken").toString(),
					requestMap.get("opType").toString(), receiveKey);
			requestObject.putAll((Map<String, Object>) requestMap.get("body"));
			webSocketService.receive(new ChannelContext(requestMap.get("accessToken").toString(), ctx), requestObject);
		} catch (Exception e) {
			LOGGER.error(request + ":" + e.getMessage(), e);
		}

	}

	/**
	 * 描述：客户端断开连接
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		for (Entry<String, ChannelContext> entry : WebSocketCached.getExpertChannelContextMap().entrySet()) {
			if (ctx.channel().id().asLongText()
					.equals(entry.getValue().getChannelHandlerContext().channel().id().asLongText())) {
				WebSocketCached.getExpertChannelContextMap().remove(entry.getKey());
				WebSocketCached.dataMap.remove(entry.getValue().getChannelHandlerContext().channel().id().asLongText());
				return;
			}
		}
	}

	/**
	 * 异常处理：关闭channel
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}

	private void sendErrorMessage(ChannelHandlerContext ctx, String errorMsg) {

		ctx.channel().writeAndFlush(new TextWebSocketFrame(GsonUtil.toJson(new ResponseObject(null).error(errorMsg))));
	}

}
